use std::{collections::HashMap, future::Future, pin::Pin};

use model::{BizRequest, BizResponse};
use serde_json::Value;

pub mod database;
pub mod error;
/// ======  mod ========
pub mod model;
pub mod utils;

pub mod crypto;
pub mod test;
pub mod validator;

pub mod page_query;
pub use page_query::*;

pub type BizHandlerResult = anyhow::Result<BizResponse<Value>>;
pub type BizFut = dyn Future<Output = BizHandlerResult> + Send + 'static;
pub type BizFutPinBox = Pin<Box<BizFut>>;

pub use sqlx;

pub trait BizHandler {
    fn service_map() -> ServiceStore;
}

/// 简化将async fn放入Map
#[macro_export]
macro_rules! service_store {
    ($( ($name:expr, $description:expr) => $func:expr ) ; *) => {
        {
            let mut store = axum_app_common::ServiceStore::new();
            $(
                store.add_func(
                    $name,$description,|p| Box::pin($func(p))
                );
            )*
            store
        }
    };
}

pub trait PageHello {
    fn say_hello();
}

pub struct ServiceStoreItem {
    pub name: String,
    pub description: String,
    pub func: fn(BizRequest) -> BizFutPinBox,
}

pub struct ServiceStore {
    service_map: HashMap<String, ServiceStoreItem>,
}

impl ServiceStore {
    pub fn new() -> Self {
        ServiceStore {
            service_map: HashMap::<String, ServiceStoreItem>::new(),
        }
    }

    pub fn add_func(
        &mut self,
        name: &str,
        description: &str,
        func: fn(BizRequest) -> BizFutPinBox,
    ) -> &mut Self {
        self.service_map.insert(
            name.to_string(),
            ServiceStoreItem {
                name: name.to_string(),
                description: description.to_string(),
                func,
            },
        );
        self
    }

    pub fn add_service_map(&mut self, map: HashMap<String, ServiceStoreItem>) {
        self.service_map.extend(map.into_iter());
    }

    pub fn add_service_store(&mut self, store: ServiceStore) {
        self.service_map.extend(store.service_map.into_iter());
    }

    pub fn get_func(&self, name: &str) -> Option<fn(BizRequest) -> BizFutPinBox> {
        let item = self.service_map.get(name);

        let Some(item) = item else {
            return None;
        };

        let func = item.func;

        Some(func)
    }

    pub fn get_service_store_info(&self) -> HashMap<String, String> {
        let map = &self.service_map;

        map.iter()
            .map(|(k, v)| (k.clone(), v.description.clone()))
            .collect::<HashMap<String, String>>()
    }
}

pub mod my_date_format {
    use aead::KeyInit;
    use chrono::{DateTime, NaiveDateTime, Utc};
    use chrono_tz::Tz;
    use serde::{self, Deserialize, Deserializer, Serializer};

    const FORMAT: &'static str = "%Y-%m-%d %H:%M:%S";

    // The signature of a serialize_with function must follow the pattern:
    //
    //    fn serialize<S>(&T, S) -> Result<S::Ok, S::Error>
    //    where
    //        S: Serializer
    //
    // although it may also be generic over the input types T.
    pub fn serialize<S>(date: &DateTime<Utc>, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        // 系统时区
        let local_tz = Tz::Asia__Shanghai;

        // 转换为本地时间
        let local_time: DateTime<Tz> = date.with_timezone(&local_tz);
        let s = format!("{}", local_time.format(FORMAT));
        serializer.serialize_str(&s)
    }

    // The signature of a deserialize_with function must follow the pattern:
    //
    //    fn deserialize<'de, D>(D) -> Result<T, D::Error>
    //    where
    //        D: Deserializer<'de>
    //
    // although it may also be generic over the output types T.
    pub fn deserialize<'de, D>(deserializer: D) -> Result<DateTime<Utc>, D::Error>
    where
        D: Deserializer<'de>,
    {
        let s = String::deserialize(deserializer)?;
        let dt = NaiveDateTime::parse_from_str(&s, FORMAT).map_err(serde::de::Error::custom)?;
        Ok(DateTime::<Utc>::from_naive_utc_and_offset(dt, Utc))
    }
}

#[cfg(test)]
mod tests {
    use std::{
        collections::HashMap,
        future::Future,
        panic::{RefUnwindSafe, UnwindSafe},
        pin::Pin,
    };

    use futures::FutureExt;
    use sea_orm::DatabaseConnection;
    use serde_json::{json, Value};

    use crate::model::*;

    struct MyReq {
        service: String,
        biz_content: Value,
        context: Option<SessionContext>,
    }
    type BizFut = dyn Future<Output = BizResponse<Value>> + Send + UnwindSafe + 'static;
    type AsyncFn = Pin<Box<BizFut>>;
    type AsyncFnMap = HashMap<&'static str, fn(MyReq) -> AsyncFn>;

    #[tokio::test]
    async fn test1() {
        let mut map = AsyncFnMap::new();
        map.insert("f1", |p| Box::pin(async_fn(p)));

        let f1 = map.get("f1").unwrap();

        let req = MyReq {
            service: "".to_string(),
            biz_content: json!({}),
            context: None,
        };

        let req1 = BizRequest::new("", json!({}), None, None);

        let res = f1(req).catch_unwind().await;

        println!("res={:?}", res);
    }

    async fn async_fn(s: MyReq) -> BizResponse<Value> {
        panic!("errrrrrr")
    }
}
